package org.yzh.scheduler.schedule;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.yzh.commons.util.JsonUtils;
import org.yzh.scheduler.mapper.ScheduleJobMapper;
import org.yzh.scheduler.model.entity.JobLogDO;
import org.yzh.scheduler.model.entity.ScheduleJobDO;
import org.yzh.scheduler.model.enums.HttpMethod;
import org.yzh.scheduler.service.JobLogService;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

@Component
public class RemoteHttpTrigger {

    private static final Logger logger = LoggerFactory.getLogger(RemoteHttpTrigger.class.getSimpleName());

    @Autowired
    private ScheduleJobMapper scheduleJobMapper;

    @Autowired
    private JobLogService jobLogService;

    @Autowired
    @Qualifier("domain")
    private String domain;

    public void trigger(String name, String group) {
        ScheduleJobDO job = scheduleJobMapper.get(name, group);
        if (job == null) {
            SchedulerUtils.delete(name, group);
            return;
        }

        JobLogDO jobLog = new JobLogDO();

        jobLog.setJobId(job.getId());
        jobLog.setJobName(job.getJobName());
        jobLog.setJobGroup(job.getJobGroup());

        jobLog.setMethod(job.getMethod());
        jobLog.setUrl(job.getUrl());
        jobLog.setContentType(job.getContentType());
        jobLog.setParameters(job.getParameters());

        jobLog.setTriggerTime(LocalDateTime.now());
        jobLog.setTriggerCode(-1);

        jobLogService.insert(jobLog);

        triggerInternal(jobLog);
    }

    private static HttpClientBuilder httpClientBuilder = HttpClients.custom()
            .disableAutomaticRetries()
            .disableRedirectHandling()
            .setDefaultRequestConfig(RequestConfig.custom()
                    .setExpectContinueEnabled(false)
                    .setRedirectsEnabled(false)
                    .setConnectTimeout(10000)
                    .setSocketTimeout(10000)
                    .build());

    private void triggerInternal(JobLogDO jobLog) {
        HttpRequestBase request = buildRequest(jobLog);

        try (CloseableHttpClient httpClient = httpClientBuilder.build();
             CloseableHttpResponse httpResponse = httpClient.execute(request)) {

            jobLog.setTriggerCode(httpResponse.getStatusLine().getStatusCode());

            HttpEntity entity = httpResponse.getEntity();
            if (entity != null)
                jobLog.setTriggerMsg(EntityUtils.toString(entity, Consts.UTF_8));

        } catch (Exception e) {
            logger.warn("Log id:{}, name:{}, group:{}, exception:{}", jobLog.getId(), jobLog.getJobName(), jobLog.getJobGroup(), e.getClass().getName());

            jobLog.setTriggerMsg(ExceptionUtils.getStackTrace(e));
            if (jobLog.getTriggerCode() == null)
                jobLog.setTriggerCode(0);

        } finally {
            jobLogService.update(jobLog);
        }
    }

    private HttpRequestBase buildRequest(JobLogDO jobLog) {
        String logKey = jobLogService.genKey(jobLog.getJobGroup(), jobLog.getJobName(), jobLog.getId(), jobLog.getTriggerTime());
        String callbackUrl = domain + "callback/" + logKey;

        HttpMethod method = jobLog.getMethod();
        ContentType contentType = jobLog.getContentType().VALUE;
        String url = jobLog.getUrl();
        String parameters = jobLog.getParameters();


        HttpRequestBase httpRequest;
        if (HttpMethod.GET == method) {
            url = setParameters(url, callbackUrl, JsonUtils.toObj(parameters, TreeMap.class));
            httpRequest = new HttpGet(url);

        } else {
            HttpEntity httpEntity;
            if (ContentType.APPLICATION_FORM_URLENCODED == contentType) {

                List<BasicNameValuePair> nameValuePairs;
                Map<String, Object> map = JsonUtils.toObj(parameters, TreeMap.class);
                if (map == null) {
                    nameValuePairs = new ArrayList(1);
                } else {
                    nameValuePairs = new ArrayList(map.size() + 1);
                    for (Map.Entry<String, Object> entry : map.entrySet()) {
                        Object value = entry.getValue();
                        if (value != null)
                            nameValuePairs.add(new BasicNameValuePair(entry.getKey(), String.valueOf(value)));
                    }
                }

                nameValuePairs.add(new BasicNameValuePair("callbackUrl", callbackUrl));
                httpEntity = new UrlEncodedFormEntity(nameValuePairs, Consts.UTF_8);

            } else {
                url = setParameters(url, callbackUrl, null);
                httpEntity = new StringEntity(parameters, contentType);
            }

            HttpPost post = new HttpPost(url);
            post.setEntity(httpEntity);

            httpRequest = post;
        }
        return httpRequest;
    }

    private static String setParameters(String url, String callbackUrl, Map<String, Object> parameters) {
        StringBuilder result = new StringBuilder(255).append(url);
        if (url.contains("?"))
            result.append("&");
        else
            result.append("?");
        if (parameters != null)
            for (Map.Entry<String, Object> entry : parameters.entrySet()) {
                Object value = entry.getValue();
                if (value != null)
                    result.append(entry.getKey()).append("=").append(value).append("&");
            }
        result.append("callbackUrl").append("=").append(callbackUrl);
        return result.toString();
    }
}